﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Reflection;

namespace Pavilion
{
    public class ScriptXmlSerializer
    {
        public XDocument SerializeScript(ContainerNode containerNode)
        {
            XDocument xmlDocument = new XDocument();

            XElement containerElement = SerializeContainerNode(containerNode);
            xmlDocument.Add(new XElement("Script", containerElement));

            return xmlDocument;
        }

        public XElement SerializeContainerNode(ContainerNode containerNode)
        {
            XElement containerElement = new XElement("ContainerNode");

            containerElement.Add(new XAttribute("Name", containerNode.Name));

            if (containerNode.GameDataCollection.Count != 0)
                containerElement.Add(SerializeGameToolDataCollection(containerNode.GameDataCollection));

            XElement nodeCollectionElement = new XElement("NodeCollection");
            containerElement.Add(nodeCollectionElement);

            foreach(NodeBase node in containerNode.NodeCollection)
            {
                if (node is ContainerNode)
                    nodeCollectionElement.Add(SerializeContainerNode((ContainerNode)node));
                else if (node is AnimationNode)
                    nodeCollectionElement.Add(SerializeAnimationNode((AnimationNode)node));
            }

            foreach (NodeBase node in containerNode.NodeCollection)
                containerElement.Add(SerializeLinkCollection(node.LinkCollection));

            return containerElement;
        }

        private XElement SerializeAnimationNode(AnimationNode animationNode)
        {
            XElement animationNodeElement = new XElement("AnimationNode");
            animationNodeElement.Add(new XAttribute("Name", animationNode.Name));
            if (animationNode.Animation != null)
                animationNodeElement.Add(new XAttribute("Animation", animationNode.Animation.Name));
            else
                animationNodeElement.Add(new XAttribute("Animation", ""));

            if (animationNode.GameDataCollection.Count != 0)
                animationNodeElement.Add(SerializeGameToolDataCollection(animationNode.GameDataCollection));

            return animationNodeElement;
        }

        private XElement SerializeGameToolDataCollection(IEnumerable<GameData> gameDataCollection)
        {
            XElement gameToolDataCollectionElement = new XElement("GameDataCollection");

            foreach (GameData gameData in gameDataCollection)
                gameToolDataCollectionElement.Add(SerializeGameToolData(gameData));

            return gameToolDataCollectionElement;
        }

        private XElement SerializeGameToolData(GameData gameData)
        {
            XElement gameToolDataElement = new XElement(gameData.DataInfo.GetType().Name);

            foreach (string key in gameData.PropertyDictionary.Keys)
                gameToolDataElement.Add(new XAttribute(key, gameData.PropertyDictionary[key]));

            return gameToolDataElement;
        }

        private IEnumerable<XElement> SerializeLinkCollection(IEnumerable<Link> linkList)
        {
            List<XElement> linkElementList = new List<XElement>();

            foreach (Link link in linkList)
                linkElementList.Add(SerializeLink(link));

            return linkElementList;
        }

        private XElement SerializeLink(Link link)
        {
            XElement linkElement = new XElement("Link");

            linkElement.Add(new XAttribute("OpenCriteria", link.OpenCriteria));
            linkElement.Add(new XAttribute("CurrentNode", link.StartingNode.Name));
            linkElement.Add(new XAttribute("NextNode", link.EndingNode.Name));

            linkElement.Add(SerializeLockCollection(link.LockCollection));

            return linkElement;
        }

        private IEnumerable<XElement> SerializeLockCollection(List<ILock> lockList)
        {
            List<XElement> lockElementList = new List<XElement>();

            foreach (ILock lockItem in lockList)
            {
                XElement lockElement = lockItem.Serialize();
                lockElement.Add(new XAttribute("Type", lockItem.GetType().Name));
                lockElementList.Add(lockElement);
            }

            return lockElementList;
        }

        public ContainerNode DeserializeScript(string scriptPath, string resourceName)
        {
            XElement rootContainerXElement = XDocument.Load(scriptPath).Element("Script").Element("ContainerNode");

            return DeserializeContainerNode(rootContainerXElement, resourceName);
        }

        public ContainerNode DeserializeContainerNode(XElement containerElement, string resourceName)
        {
            ContainerNode containerNode = new ContainerNode(containerElement.Attribute("Name").Value);

            XElement nodeCollectionElement = containerElement.Element("NodeCollection");
            IEnumerable<XElement> containerElementList = nodeCollectionElement.Elements("ContainerNode");
            IEnumerable<XElement> animationElementList = nodeCollectionElement.Elements("AnimationNode");

            foreach (XElement containerChildElement in containerElementList)
                containerNode.AddNode(DeserializeContainerNode(containerChildElement, resourceName));

            foreach (XElement animationChildElement in animationElementList)
                containerNode.AddNode(DeserializeAnimationNode(animationChildElement, resourceName));

            DeserializeLinkCollection(containerElement, containerNode, resourceName);
            DeserializeGameDataCollection(containerElement.Element("GameDataCollection"), containerNode);

            return containerNode;
        }

        private AnimationNode DeserializeAnimationNode(XElement animationNodeElement, string resourceName)
        {
            string animationNodeName = animationNodeElement.Attribute("Name").Value;
            string animationName = animationNodeElement.Attribute("Animation").Value;
            AnimationResource animationResource = ResourceManager.Instance[resourceName] as AnimationResource;

            AnimationNode animationNode = new AnimationNode(animationNodeName);

            if (animationResource.Contains(animationName))
                animationNode.Animation = animationResource[animationName];

            DeserializeGameDataCollection(animationNodeElement.Element("GameDataCollection"), animationNode);

            return animationNode;
        }

        private void DeserializeGameDataCollection(XElement gameToolDataCollectionElement, NodeBase node)
        {
            if (gameToolDataCollectionElement == null)
                return;

            foreach (XElement gameToolDataElement in gameToolDataCollectionElement.Elements())
                node.GameDataCollection.Add(DeserializeGameData(gameToolDataElement));
        }

        private GameData DeserializeGameData(XElement gameToolDataElement)
        {
            GameData createdGameData = new GameData(GameEngine.CreateInstance<IGameDataInfo>("Pavilion.GameTools.dll", gameToolDataElement.Name.LocalName));

            foreach (XAttribute attribute in gameToolDataElement.Attributes())
                createdGameData.AddData(attribute.Name.LocalName, attribute.Value);

            return createdGameData;
        }

        private void DeserializeLinkCollection(XElement containerElement, ContainerNode containerNode, string resourceName)
        {
            IEnumerable<XElement> linkElementList = containerElement.Elements("Link");

            foreach (XElement linkElement in linkElementList)
                DeserializeLink(linkElement, containerNode, resourceName);
        }

        private void DeserializeLink(XElement linkElement, ContainerNode containerNode, string resourceName)
        {
            OpenCriteria openCriteria = (OpenCriteria)Enum.Parse(typeof(OpenCriteria), linkElement.Attribute("OpenCriteria").Value);
            string currentNodeName = linkElement.Attribute("CurrentNode").Value;
            string nextNodeName = linkElement.Attribute("NextNode").Value;
            IEnumerable<XElement> lockElementList = linkElement.Elements("Lock");

            IEnumerable<ILock> lockList = DeserializeLockCollection(lockElementList, resourceName);
            containerNode.LinkNode(currentNodeName, nextNodeName, lockList, openCriteria);
        }

        private IEnumerable<ILock> DeserializeLockCollection(IEnumerable<XElement> lockElementList, string resourceName)
        {
            List<ILock> lockList = new List<ILock>();

            foreach (XElement lockElement in lockElementList)
                lockList.Add(DeserializeLock(lockElement, resourceName));

            return lockList;
        }

        private ILock DeserializeLock(XElement lockElement, string resourceName)
        {
            string lockName = lockElement.Attribute("Type").Value;
            ILock createdLock = GameEngine.CreateInstance<ILock>("Pavilion.Locks.dll", lockName);
            createdLock.Deserialize(lockElement, resourceName);

            return createdLock;
        }
    }
}
